package net.carinae.dev.seduku.utils;

import java.util.EnumSet;
import java.util.Set;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;


/**
 * Supported sudoku types by the generator.
 * 
 * @author Carlos Vara
 */
public enum SudokuType {

    FOUR_BY_FOUR {

        @Override
        public SudokuDigit[] allDigits() {
            return Sudoku4Digit.values();
        }

        @Override
        public SquareIndex[] allIndexes() {
            return Sudoku4Index.values();
        }

        @Override
        public SudokuDigit digitValueOf(String val) {
            return Sudoku4Digit.valueOf(val);
        }
    },
    NINE_BY_NINE {

        @Override
        public SudokuDigit[] allDigits() {
            return Sudoku9Digit.values();
        }

        @Override
        public SquareIndex[] allIndexes() {
            return Sudoku9Index.values();
        }

        @Override
        public SudokuDigit digitValueOf(String val) {
            return Sudoku9Digit.valueOf(val);
        }
    };
    
    public abstract SudokuDigit[] allDigits();
    
    public abstract SquareIndex[] allIndexes();
    
    public abstract SudokuDigit digitValueOf(String val);
    
    
    /**
     * Only the digits 1 to 4 can be assigned.
     */
    public static enum Sudoku4Digit implements SudokuDigit {

        // Numbers 1 to 4
        d1, d2, d3, d4;
        
        @Override
        public String toString() {
            return "" + (this.ordinal()+1);
        }

        @Override
        public Set<? extends SudokuDigit> complement() {
            EnumSet<Sudoku4Digit> ret = EnumSet.allOf(Sudoku4Digit.class);
            ret.remove(this);
            return ret;
        }
        
    }
    
    // --- A 4x4 SUDOKU --------------------------------------------------------
    
    /**
     * A 4x4 board.
     */
    public static enum Sudoku4Index implements SquareIndex {
        
        A1, A2, A3, A4,
        B1, B2, B3, B4,
        C1, C2, C3, C4,
        D1, D2, D3, D4;
        

        public static String ROWS = "ABCD";
        public static String COLS = "1234";
        
        public Set<Sudoku4Index> unit(SudokuUnits unit) {
            
            String thisRep = this.toString();
            
            switch ( unit ) {
                case COL:
                    return cross(ROWS, "" + thisRep.charAt(1));
                case ROW:
                    return cross("" + thisRep.charAt(0), COLS);
                case BOX:
                    String bRow = (-1 != "AB".indexOf("" + thisRep.charAt(0))) ? "AB" : "CD";
                    String bCol = (-1 != "12".indexOf("" + thisRep.charAt(1))) ? "12" : "34";
                    return cross(bRow, bCol);
                default:
                    throw new IllegalArgumentException("Invalid unit: " + unit);
            }
            
        }
        
        public SetMultimap<SudokuUnits, Sudoku4Index> units() {
            
            SetMultimap<SudokuUnits, Sudoku4Index> ret = HashMultimap.create(3, 4);
            
            for ( SudokuUnits unit : SudokuUnits.values() ) {
                ret.putAll(unit, unit(unit));
            }
            
            return ret;
        }
        
        public Set<Sudoku4Index> peers() {
            EnumSet<Sudoku4Index> ret = EnumSet.copyOf(units().values());
            ret.remove(this);
            return ret;
        }

        /**
         * Cross product of the characters in each String.
         * 
         * @param a
         *            First array of characters, as a string.
         * @param b
         *            Second array of characters, as a string
         * @return The result of the cross product, an array of 2 char strings.
         */
        public static EnumSet<Sudoku4Index> cross(String a, String b) {

            EnumSet<Sudoku4Index> ret = EnumSet.noneOf(Sudoku4Index.class);
            for (int ia = 0; ia < a.length(); ia++) {
                for (int ib = 0; ib < b.length(); ib++) {
                    ret.add(Sudoku4Index.valueOf("" + a.charAt(ia) + b.charAt(ib)));
                }
            }

            return ret;
        }
        
    }
    
    
    // --- A 9x9 SUDOKU --------------------------------------------------------
    
    /**
     * Only the digits 1 to 9 can be assigned.
     */
    public static enum Sudoku9Digit implements SudokuDigit {

        // Numbers 1 to 9
        d1, d2, d3, d4, d5, d6, d7, d8, d9;
        
        @Override
        public String toString() {
            return "" + (this.ordinal()+1);
        }

        @Override
        public Set<? extends SudokuDigit> complement() {
            EnumSet<Sudoku9Digit> ret = EnumSet.allOf(Sudoku9Digit.class);
            ret.remove(this);
            return ret;
        }
        
    }
    
    /**
     * A 9x9 board.
     */
    public static enum Sudoku9Index implements SquareIndex {
        
        A1, A2, A3, A4, A5, A6, A7, A8, A9,
        B1, B2, B3, B4, B5, B6, B7, B8, B9,
        C1, C2, C3, C4, C5, C6, C7, C8, C9,
        D1, D2, D3, D4, D5, D6, D7, D8, D9,
        E1, E2, E3, E4, E5, E6, E7, E8, E9,
        F1, F2, F3, F4, F5, F6, F7, F8, F9,
        G1, G2, G3, G4, G5, G6, G7, G8, G9,
        H1, H2, H3, H4, H5, H6, H7, H8, H9,
        I1, I2, I3, I4, I5, I6, I7, I8, I9;
        

        public static String ROWS = "ABCDEFGHI";
        public static String COLS = "123456789";
        
        public Set<Sudoku9Index> unit(SudokuUnits unit) {
            
            String thisRep = this.toString();
            
            switch ( unit ) {
                case COL:
                    return cross(ROWS, "" + thisRep.charAt(1));
                case ROW:
                    return cross("" + thisRep.charAt(0), COLS);
                case BOX:
                    String bRow = (-1 != "ABC".indexOf("" + thisRep.charAt(0))) ? "ABC" : (-1 != "DEF".indexOf("" + thisRep.charAt(0)))?"DEF":"GHI";
                    String bCol = (-1 != "123".indexOf("" + thisRep.charAt(1))) ? "123" : (-1 != "456".indexOf("" + thisRep.charAt(1)))?"456":"789";
                    return cross(bRow, bCol);
                default:
                    throw new IllegalArgumentException("Invalid unit: " + unit);
            }
            
        }
        
        public SetMultimap<SudokuUnits, Sudoku9Index> units() {
            
            SetMultimap<SudokuUnits, Sudoku9Index> ret = HashMultimap.create(3, 9);
            
            for ( SudokuUnits unit : SudokuUnits.values() ) {
                ret.putAll(unit, unit(unit));
            }
            
            return ret;
        }
        
        public Set<Sudoku9Index> peers() {
            EnumSet<Sudoku9Index> ret = EnumSet.copyOf(units().values());
            ret.remove(this);
            return ret;
        }

        /**
         * Cross product of the characters in each String.
         * 
         * @param a
         *            First array of characters, as a string.
         * @param b
         *            Second array of characters, as a string
         * @return The result of the cross product, an array of 2 char strings.
         */
        public static EnumSet<Sudoku9Index> cross(String a, String b) {

            EnumSet<Sudoku9Index> ret = EnumSet.noneOf(Sudoku9Index.class);
            for (int ia = 0; ia < a.length(); ia++) {
                for (int ib = 0; ib < b.length(); ib++) {
                    ret.add(Sudoku9Index.valueOf("" + a.charAt(ia) + b.charAt(ib)));
                }
            }

            return ret;
        }
        
    }
    
}
